#pragma once
#ifndef _UTILS_H
#define _UTILS_H

#include <utility>
#include <ostream>
#include <stack>
#include <vector>
#include <string>
#include <ctime>
#include <sstream>

#include <boost/serialization/is_bitwise_serializable.hpp>
#include <boost/serialization/access.hpp>

namespace utils
{

	typedef std::pair<unsigned, unsigned> coords_t;

	std::ostream& operator<<(std::ostream& os, const coords_t &c)
	{
		os << "[" << c.first << "," << c.second << "]";
		return os;
	}

	template<class T>
	std::ostream& printVector(std::ostream& os, const std::vector<T> &v) {
		os << '[';
		for (unsigned i = 0; i < v.size(); i++) {
			os << v[i] << ':';
		}
		os << "]\n";
		return os;
	}

	//x,y - souradnice, n - velikost CTVERCOVE matice
	inline unsigned coordsToIndex(const unsigned x, const unsigned y, const unsigned n)
	{
		return y * n + x;
	}

	inline unsigned coordsToIndex(const coords_t& _coor, const unsigned n)
	{
		return coordsToIndex(_coor.first, _coor.second, n);
	}

	//i - index v 1rozmernem poli, n-velikost ctvercove matice
	coords_t indexToCoords(unsigned i, unsigned n)
	{
		return coords_t(i%n,i/n);
	}

	/////////////////////////////////////////////////////////////
	//Metoda pro vypis zasobniku do streamu
	template <class T>
	void dumpStack(const std::stack<T> &st, std::ostream &os, const unsigned cntr)
	{
		os << cntr << ":\n";
		os << st.top().boardLayout << "\n";
		os << st.top() << "\n";
	}

	//////////////////////////////////////////////////////////////////////////
	// Funkce pro zalogovani nejake zpravy, prip. objektu, co ma definovan
	// vypis do streamu.
	template <class T>
	void log(const T& msg, std::ostream& os = std::cout)
	{
		time_t rawtime;
		struct tm ti;
		time(&rawtime);
		localtime_s(&ti, &rawtime);

		os <<
			"[" << ti.tm_hour<< ":" << ti.tm_min << ":" << ti.tm_sec << "]:\n"
			<< msg << "\n";
	}
	
	//////////////////////////////////////////////////////////////////////////
	//
	template<class T>
	std::string toString(const T& t)
	{
		std::stringstream s;
		s << t;
		return s.str();
	}
	template<class T1, class T2>
	std::string toString(const T1& t1, const T2& t2)
	{
		std::stringstream s;
		s << t1 << t2;
		return s.str();
	}
	template<class T1, class T2, class T3>
	std::string toString(const T1& t1, const T2& t2, const T3& t3)
	{
		std::stringstream s;
		s << t1 << t2 << t3;
		return s.str();
	}

	//////////////////////////////////////////////////////////////////////////
	//
	class Board
	{

		friend class boost::serialization::access;

		//Pocet bunek v poli, pro 2 je max. velikost Boardu 11x11, pro 4 16x16.
		static const unsigned STATIC_SIZE = 4;

		//Typ polozek v poli, drzicich bity.
		typedef unsigned long long backing_t;

		//Tato konstanta se rovna logaritmu poctu bitu v backing typu (64),
		//coz je zaroven velikost posuvu vpravo, ktery odpovida deleni poctem bitu.
		static const backing_t BITSHIFT=6;

		//Maska s poctem jednicek zprava, odpovidajici BITSHIFT.
		//Operaci AND s touto konstantou odpovida operaci modulo velikost backing typu (64).
		static const backing_t MODULO_MASK=0x3FUL;
		
		//Pole obsahujici jednotlive bity, pouzita staticka velikost,
		//za mensi univerzalnost ziskame rychlejsi serializaci a vytvoreni objektu.
		backing_t _backingArray[STATIC_SIZE];

		//Pocet policek (bitu).
		unsigned _size;

		//////////////////////////////////////////////////////////////////////////
		//Index bitu v bloku, jenz odpovida zadanemu globalnimu indexu bitu.
		inline unsigned bit_index(const unsigned i) const
		{
			//operace AND s cislem se 6 jednickami vpravo (0x3F) odpovida zbytky po deleni 64
			return i&MODULO_MASK;
		}
		
		//////////////////////////////////////////////////////////////////////////
		//Index bloku v backing array, odpovidajici zadanemu globalnimu indexu.
		inline unsigned block_index(const unsigned i) const
		{
			//posun o 6 doprava odpovida deleni 64, coz odpovida poctu bitu v unsigned long long
			return i >> BITSHIFT;
		}

		//////////////////////////////////////////////////////////////////////////
		//
		template<class Archive>
		inline void serialize(Archive& ar, const unsigned int version)
		{
			ar & _backingArray & _size;
		}

	public:
		//////////////////////////////////////////////////////////////////////////
		//Default ctor
		Board():_size(0)
		{}
		
		//////////////////////////////////////////////////////////////////////////
		//Ctor pro danou velikost.
		Board(unsigned s): _size(s)
		{
			if (s>sizeof(backing_t)*8*STATIC_SIZE)
			{
				throw "Pozadovana velikost je vetsi nez maximalni mozna.";
			}
			for(unsigned i = 0; i<STATIC_SIZE; ++i)
			{
				_backingArray[i] = 0ULL;
			}
		}

		//////////////////////////////////////////////////////////////////////////
		//Copy ctor.
		Board(const Board &that)
		{
			this->_size = that._size;
			for (unsigned i = 0; i<STATIC_SIZE; i++)
			{
				this->_backingArray[i] = that._backingArray[i];
			}
		}

		//////////////////////////////////////////////////////////////////////////
		//Operator prirazeni
		Board& operator= (const Board &b)
		{
			_size = b._size;
			for (unsigned i=0; i<STATIC_SIZE; ++i)
			{
				_backingArray[i] = b._backingArray[i];
			}
			return *this;
		}

		//////////////////////////////////////////////////////////////////////////
		//Vraci true, kdyz neni zadny bit nastaven na true.
		bool none() const {
			return !any();
		}

		//////////////////////////////////////////////////////////////////////////
		//Vraci true, kdyz je aspon 1 bit nekde nastaven na true.
		bool any() const
		{
			for (unsigned i = 0; i<STATIC_SIZE; ++i)
			{
				if (_backingArray[i])
				{
					return true;
				}
			}
			return false;
		}

		//////////////////////////////////////////////////////////////////////////
		//Hodnota bitu na i-te pozici
		bool at(const unsigned i) const
		{
			if (i>=_size)
			{
				throw "Index mimo rozsah";
			}
			return (_backingArray[block_index(i)] >> bit_index(i)) & 1L;
		}

		//////////////////////////////////////////////////////////////////////////
		//Operator[] je implementovan jen pro cteni, pro zapis pouzit metodu setAt.
		bool operator[] (const unsigned i) const
		{
			return at(i);
		}

		//////////////////////////////////////////////////////////////////////////
		//Nastavi bit na i-te pozici na hodnotu b.
		void setAt(const unsigned i, const bool b)
		{
			if (i>=_size)
			{
				throw "Index mimo rozsah";
			}
			
			if (b)
			{
				backing_t tmp = 1ULL << bit_index(i);
				_backingArray[block_index(i)] |= tmp;
			}
			else
			{
				backing_t tmp = ~(1ULL << bit_index(i));
				_backingArray[block_index(i)] &= tmp;
			}
		}

		//////////////////////////////////////////////////////////////////////////
		//Operator porovnani.
		bool operator== (const Board &b) const
		{
			if (_size!=b._size)
			{
				return false;
			}
			for (unsigned i = 0; i<STATIC_SIZE; ++i)
			{
				if (_backingArray[i] != b._backingArray[i])
				{
					return false;
				}
			}
			return true;
		}

		bool operator!= (const Board& that) const
		{
			return !(*this==that);
		}

		//////////////////////////////////////////////////////////////////////////
		//Pocet bitu v bitovem poli
		unsigned size() const
		{
			return _size;
		}

		//////////////////////////////////////////////////////////////////////////
		//Dtor.
		virtual ~Board()
		{
		}

		//////////////////////////////////////////////////////////////////////////
		//printing
		friend std::ostream& operator<< (std::ostream& os, const Board& b)
		{
			for (unsigned i = b._size; i>0; i--)
			{
				os << (b.at(i-1) ? '1' : '0');
			}
			return os;
		}
	};	//End of utils::Board


}	//End of namespace utils

//Makro, ktere prida trait, ze je trida serializovatelna po bitech,
//tedy neobsahuje pointry ani netreba zkoumat verzi a tak dal...
BOOST_IS_BITWISE_SERIALIZABLE(utils::Board);


#endif // _UTILS_H
